package sim.portrayal;

import java.util.*;
import javax.swing.*;
import java.awt.*;
import sim.util.gui.*;
import sim.display.*;

/**
 * A FieldPortrayal is an object which knows how to portray some kind of Field.
 * 
 * <p>
 * This abstract version of FieldPortrayal provides some basic functionality
 * that many FieldPortrayals may find handy. This functionality allows a
 * FieldPortrayal to store Portrayal objects responsible for drawing various
 * objects within the Field. For example, a SparseGrid2D holds a bunch of
 * objects: the SparseGridPortrayal2D, which is a FieldPortrayal, lets you store
 * Portrayal objects which know how to draw the various objects in the
 * SparseGrid2D.
 * 
 * <p>
 * You can associate a Portrayal object with an object stored in the Field in
 * several ways. First, you can specify one Portrayal object to be used for ALL
 * objects stored in the field, using <b>setPortrayalForAll</b>. Second, you can
 * specify a Portrayal object to be used for objects in a Field all belonging to
 * the same class, using <b>setPortrayalForClassOf</b>. Third, you can specify a
 * Portrayal for a specific object, using <b>setPortrayalForObject</b>.
 * 
 * <p>
 * You can get the desired Portrayal for an object by calling
 * <b>getPortrayalForObject</b>. This method looks up the Portrayal for an
 * object by going down the following checklist until a Portrayal is found
 * (earlier checklist items take precedence over later ones):
 * 
 * <ol>
 * <li>
 * If there is a portrayalForAll, return it.
 * <li>
 * If the object implements the appropriate Portrayal interface, return the
 * object itself as its own Portrayal.
 * <li>
 * If a portrayal is explicitly registered for the object, return that
 * portrayal. Portrayals may be registered for <tt>null</tt> as well.
 * <li>
 * If the object is null:
 * <ol>
 * <li>Return the portrayalForNull if there is one
 * <li>Return the defaultNullPortrayal.
 * </ol>
 * <li>
 * If a Portrayal is registered for the object's exact class (superclasses are
 * ignored), return that portrayal.
 * <li>
 * Return the portrayalForRemainder if there is one
 * <li>
 * Return the default Portrayal object.
 * </ol>
 * 
 * <p>
 * FieldPortrayals store Portrayal objects in WeakHashMaps. This means that if
 * you register a Portrayal explicitly for an object, and then later the object
 * is eliminated from your model, the FieldPortrayal will not hold onto the
 * object or onto its Portrayal, but will allow them to garbage collect as well.
 * Thus you don't have to worry about de-registering an object.
 * 
 * <p>
 * Some FieldPortrayals benefit (draw faster) if they know that their underlying
 * field is immutable, that is, it never changes. Notably, most
 * FieldPortrayal3Ds benefit, as well as various ValueGrid2DPortrayals.
 */

public abstract class FieldPortrayal {
	public Portrayal portrayalForAll;
	public Portrayal portrayalForNull;
	public Portrayal portrayalForRemainder;
	public WeakHashMap portrayals = new WeakHashMap();
	public WeakHashMap classPortrayals = new WeakHashMap();

	/** Set the portrayal to null to remove it. */
	public void setPortrayalForAll(Portrayal portrayal) {
		portrayalForAll = portrayal;
	}

	public Portrayal getPortrayalForAll() {
		return portrayalForAll;
	}

	/** Set the portrayal to null to remove it. */
	public void setPortrayalForNull(Portrayal portrayal) {
		portrayalForNull = portrayal;
	}

	public Portrayal getPortrayalForNull() {
		return portrayalForNull;
	}

	/** Set the portrayal to null to remove it. */
	public void setPortrayalForRemainder(Portrayal portrayal) {
		portrayalForRemainder = portrayal;
	}

	public Portrayal getPortrayalForRemainder() {
		return portrayalForRemainder;
	}

	/**
	 * Sets a portrayal for a class -- objects must be of EXACTLY this class
	 * (not subclasses) to respond to this. Set the portrayal to null to remove
	 * it for a given class.
	 */
	public void setPortrayalForClass(Class cls, Portrayal portrayal) {
		if (portrayal == null)
			classPortrayals.remove(cls);
		else
			classPortrayals.put(cls, portrayal);
	}

	/**
	 * Sets a portrayal for a class -- objects must be equal(...) to the
	 * provided object here to respond to this. Set the portrayal to null to
	 * remove it for a given object.
	 */
	public void setPortrayalForObject(Object obj, Portrayal portrayal) {
		if (portrayal == null)
			portrayals.remove(obj);
		else
			portrayals.put(obj, portrayal);
	}

	/**
	 * Returns a default portrayal for null. By default this is set to the same
	 * as getDefaultPortrayal(). Override this to provide a more interesting
	 * default portrayals for null.
	 */
	public Portrayal getDefaultNullPortrayal() {
		return getDefaultPortrayal();
	}

	/**
	 * Should return a portrayal which can portray any object regardless of
	 * whether it's valid or not
	 */
	public abstract Portrayal getDefaultPortrayal();

	/** Returns the appropriate Portrayal. */
	public Portrayal getPortrayalForObject(Object obj) {
		Portrayal portrayal;
		Portrayal tmp;

		// return the portrayal-for-all if any
		if (portrayalForAll != null)
			return portrayalForAll;

		// if the object is a portrayal itself
		else if (obj != null && obj instanceof Portrayal)
			return (Portrayal) obj;

		// return any portrayal registered for the object
		else if ((tmp = ((Portrayal) (portrayals.get(obj)))) != null)
			return tmp;

		else if (obj == null) {
			// if the object is null, return the portrayalForNull if any
			if (portrayalForNull != null)
				return portrayalForNull;

			// else return the default portrayal for null objects
			return getDefaultNullPortrayal();
		}

		// return any portrayal registered for the object's class
		else if ((tmp = ((Portrayal) (classPortrayals.get(obj.getClass())))) != null)
			return tmp;

		else if (portrayalForRemainder != null)
			return portrayalForRemainder;

		// return a default portrayal
		else
			return getDefaultPortrayal();
	}

	protected Object field = null;
	protected boolean immutableField = false;

	/**
	 * This flag is available for Fields to set and clear as they like: but its
	 * intended function is to be set during setField(field) to warn drawing
	 * that even though the field is immutable, it may have changed to another
	 * field and needs to be redrawn. Similarly, typically this flag is cleared
	 * after drawing. Initially true.
	 */
	protected boolean dirtyField = true;

	/**
	 * Returns true if the underlying field is assumed to be unchanging -- thus
	 * there's no reason to update once we're created. Not all FieldPortrayals
	 * will care about whether or not a field is immutable.
	 */
	public boolean isImmutableField() {
		return immutableField;
	}

	/**
	 * Specifies that the underlying field is (or is not) to be assumed
	 * unchanging -- thus there's no reason to update once we're created. Not
	 * all FieldPortrayals will care about whether or not a field is immutable.
	 * Sets dirtyField to true.
	 */
	public void setImmutableField(boolean val) {
		immutableField = val;
		dirtyField = true;
	}

	/** Returns the field. */
	public Object getField() {
		return field;
	}

	/**
	 * Sets the field. Also sets dirtyField = true. May throw an exception if
	 * the field is inappropriate.
	 */
	public abstract void setField(Object field);

	public class CustomInspector extends Inspector {
		/**
		 * 
		 */
		private static final long serialVersionUID = -8227897219679379189L;
		public JLabel positions = new JLabel();
		public LabelledList fieldComponent = new LabelledList("Location");
		public Inspector objectInspector;
		public LocationWrapper wrapper;
		public Object lastObject; // check to see if it's changed
		public GUIState state;

		public CustomInspector(final LocationWrapper wrapper, final Inspector objectInspector, final GUIState state) {
			this.state = state;
			this.wrapper = wrapper;
			this.objectInspector = objectInspector;
			this.state = state;
			lastObject = wrapper.getObject();
			setLayout(new BorderLayout());
			fieldComponent.addLabelled("", positions);
			add(fieldComponent, BorderLayout.NORTH);
			add(objectInspector, BorderLayout.CENTER);
			updateInspector();
		}

		public void updateInspector() {
			Object newObject = wrapper.getObject();
			if (newObject != lastObject) // a new object! Get new inspector just
											// in case
			{
				// Maybe we should revisit this -- if we dynamically remove and
				// then
				// add a new object like we're doing, then stuff gets really
				// flashy -- and
				// in some cases things don't update right (especially if you
				// load into a
				// separate window, hmmmm). We'll have to deal with the issue
				// anyway because
				// due to the same bug in Java, dynamically changing the values
				// of an array
				// inspector weirds out.
				remove(objectInspector);
				objectInspector = getPortrayalForObject(wrapper.getObject()).getInspector(wrapper, state);
				add(objectInspector, BorderLayout.CENTER);
				revalidate();
			}
			positions.setText(wrapper.getLocationName());
			objectInspector.updateInspector();
		}
	}

	public Inspector getInspector(LocationWrapper wrapper, GUIState state) {
		if (wrapper == null)
			return null;
		Inspector objectInspectorComponent = getPortrayalForObject(wrapper.getObject()).getInspector(wrapper, state);
		if (objectInspectorComponent == null)
			return null;
		return new CustomInspector(wrapper, objectInspectorComponent, state);
	}

	public String getName(LocationWrapper wrapper) {
		if (wrapper == null)
			return "";
		return getPortrayalForObject(wrapper.getObject()).getName(wrapper);
	}

	public boolean setSelected(LocationWrapper wrapper, boolean selected) {
		if (wrapper == null)
			return false;
		return getPortrayalForObject(wrapper.getObject()).setSelected(wrapper, selected);
	}
}
